package com.niiwoo.civet.trade.service.dubbo.invest;

import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.config.annotation.Service;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Sets;
import com.niiwoo.activity.stage.dto.request.notice.NoviceTaskRecordRequestDTO;
import com.niiwoo.activity.stage.enums.activity.NoviceTaskPrizeEnum;
import com.niiwoo.civet.account.dto.response.AccountBaseResponse;
import com.niiwoo.civet.account.service.AccountDubboService;
import com.niiwoo.civet.account.service.AccountInvestDubboService;
import com.niiwoo.civet.base.enums.ChannelCodeEnum;
import com.niiwoo.civet.trade.constant.RabbitConstant;
import com.niiwoo.civet.trade.constant.TradeRedisKey;
import com.niiwoo.civet.trade.dao.entity.ProjectContract;
import com.niiwoo.civet.trade.dao.entity.ProjectInvesting;
import com.niiwoo.civet.trade.dao.entity.UserInvestConfig;
import com.niiwoo.civet.trade.dao.entity.UserInvestPrizeConfig;
import com.niiwoo.civet.trade.dto.BatchInvestFinishedMsg;
import com.niiwoo.civet.trade.dto.common.InvestDefaultConfigDTO;
import com.niiwoo.civet.trade.dto.common.PeriodValue;
import com.niiwoo.civet.trade.dto.common.SysInvestConfigDTO;
import com.niiwoo.civet.trade.dto.request.AutoInvestSettingRequestDTO;
import com.niiwoo.civet.trade.dto.request.BatchInvestRequestDTO;
import com.niiwoo.civet.trade.dto.request.InvestDubboRequest;
import com.niiwoo.civet.trade.dto.request.InvestPriorityChangeRequest;
import com.niiwoo.civet.trade.dto.request.contract.CommissionAuthHistoryRequestDTO;
import com.niiwoo.civet.trade.dto.response.UserAutoInvestConfigDTO;
import com.niiwoo.civet.trade.dto.response.UserBatchInvestConfigDTO;
import com.niiwoo.civet.trade.dto.response.contract.CommissionAuthAgreementResponseDTO;
import com.niiwoo.civet.trade.dto.response.invest.ProjectStartInvestAndIncreResponseDTO;
import com.niiwoo.civet.trade.enums.*;
import com.niiwoo.civet.trade.enums.FadadaContractEnum.TradeTypeEnum;
import com.niiwoo.civet.trade.service.invest.AutoInvestDubboService;
import com.niiwoo.civet.trade.service.local.common.InvestCommonServer;
import com.niiwoo.civet.trade.service.local.configure.TradeConfigLocalService;
import com.niiwoo.civet.trade.service.local.invest.AutoBatchInvestService;
import com.niiwoo.civet.trade.service.local.invest.InvestPrizeService;
import com.niiwoo.civet.trade.service.local.invest.InvestQueueService;
import com.niiwoo.civet.trade.service.local.invest.InvestService;
import com.niiwoo.civet.trade.service.local.myloan.MyLoanLocalService;
import com.niiwoo.tripod.base.enums.UserTypeEnum;
import com.niiwoo.tripod.lanmao.enums.UserAuthorizationEnum;
import com.niiwoo.tripod.lanmao.enums.UserRoleEnum;
import com.niiwoo.tripod.provider.dto.response.PageResponseDTO;
import com.niiwoo.tripod.provider.exception.BizException;
import com.niiwoo.tripod.service.component.SnowflakeIdWorker;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.Assert;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * Created by zhangwanli on 2017/11/23.
 */
@Service(version = "1.0.0", validation = "true")
public class AutoInvestDubboServiceImpl implements AutoInvestDubboService {
    private static final Logger logger = LoggerFactory.getLogger(AutoInvestDubboServiceImpl.class);

    @Reference(version = "1.0.0")
    private AccountDubboService accountDubboService;
    @Reference(version = "1.0.0")
    private AccountInvestDubboService accountInvestDubboService;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private InvestService investService;
    @Autowired
    private AutoBatchInvestService autoBatchInvestService;
    @Autowired
    private InvestQueueService investQueueService;
    @Autowired
    private TradeConfigLocalService tradeConfigLocalService;
    @Autowired
    private SnowflakeIdWorker snowflakeIdWorker;
	@Autowired
	private InvestPrizeService investPrizeService;
	@Autowired
	private InvestCommonServer investCommonServer;
	@Autowired
	private MyLoanLocalService myLoanLocalService;

	@Override
	public UserAutoInvestConfigDTO queryUserAutoInvestConfig(String userId) {
		UserAutoInvestConfigDTO dto = new UserAutoInvestConfigDTO();
		// 所有系统方案配置
		List<SysInvestConfigDTO> sysConfigList = autoBatchInvestService.querySysInvestConfigDTOList();
		dto.setSysConfigList(sysConfigList);
		// 用户设定
		UserInvestConfig userCfg = autoBatchInvestService.queryUserInvestConfig(userId, InvestWayEnum.AUTO);
		UserAutoInvestConfigDTO.UserConfig userConfig = new UserAutoInvestConfigDTO.UserConfig();
		dto.setEnabledSystem(Byte.valueOf("0"));
		dto.setReadFlag(Byte.valueOf("1"));
		if(Objects.nonNull(userCfg)) {
			dto.setEnabledSystem(userCfg.getEnableSystem());
			dto.setReadFlag(userCfg.getReadFlag());
		}
		
		if (userCfg != null && Byte.valueOf("0").equals(userCfg.getEnableSystem())) {
			userConfig.setEnabled(userCfg.getEnableAuto());
			userConfig.setSingleMaxInvestAmount(userCfg.getSingleMaxInvestAmount());
			userConfig.setMinKeepBalance(userCfg.getMinKeepBalance());
			UserInvestPrizeConfig prizeCfg = autoBatchInvestService.queryUserInvestPrizeConfig(userId, InvestWayEnum.AUTO);
			if (prizeCfg != null) {
				userConfig.setAutoUsePrize(prizeCfg.getIsAutoUsePrize());
				userConfig.setMustUsePrize(prizeCfg.getIsMustUsePrize());
				userConfig.setUseCouponMinInvestAmount(prizeCfg.getUseCouponMinInvestAmount());
			}
			// 用户自动投资配置
			// 选中的标的类型
			List<Byte> productTypes = new ArrayList<>();
			// 选中的期限
			List<PeriodValue> periodTypes = new ArrayList<>();
			// 选中的还款方式
			List<Byte> repaymentTypes = new ArrayList<>();
			// 选中的利率
			List<BigDecimal> ratetTypes = new ArrayList<>();
			userConfig.setProductTypes(productTypes);
			userConfig.setPeriodTypes(periodTypes);
			userConfig.setRepaymentTypes(repaymentTypes);
			userConfig.setRatetTypes(ratetTypes);
			autoBatchInvestService.generateUserConfigDetailForList(productTypes, periodTypes, repaymentTypes, ratetTypes, userCfg.getId());

			dto.setUserConfig(userConfig);
		}
		dto.setDefaultConfig(queryInvestDefaultConfig());
		ProjectStartInvestAndIncreResponseDTO startInvestAndIncreResponse= myLoanLocalService.getProjectStartInvestAndIncrement();
		dto.setMinInvestAmount(startInvestAndIncreResponse.getStartInvestMoney());
		return dto;
	}

    private InvestDefaultConfigDTO queryInvestDefaultConfig() {
        InvestDefaultConfigDTO dto = new InvestDefaultConfigDTO();
        Map<String, String> configMap = tradeConfigLocalService.selectTradeConfigByEmuns(TradeConfigEnum.INVESTMAXAMOUNT, TradeConfigEnum.ACCOUNTMINAMOUNT, TradeConfigEnum.INVESTOREXTRAMINAMOUNT);
        if (configMap != null) {
            String investMax = configMap.get(TradeConfigEnum.INVESTMAXAMOUNT.getConfigKey());
            String accountMin = configMap.get(TradeConfigEnum.ACCOUNTMINAMOUNT.getConfigKey());
            String useCouponMinInvestAmount = configMap.get(TradeConfigEnum.INVESTOREXTRAMINAMOUNT.getConfigKey());
            if (StringUtils.isNotBlank(investMax)) {
                dto.setSingleMaxInvestAmount(new BigDecimal(investMax));
            }
            if (StringUtils.isNotBlank(accountMin)) {
                dto.setMinKeepBalance(new BigDecimal(accountMin));
            }
            if (StringUtils.isNotBlank(useCouponMinInvestAmount)) {
                dto.setUseCouponMinInvestAmount(new BigDecimal(useCouponMinInvestAmount));
            }
        }
        return dto;
    }

    @Override
    public UserBatchInvestConfigDTO queryUserBatchInvestConfig(String userId) {
        UserBatchInvestConfigDTO response = new UserBatchInvestConfigDTO();
        List<SysInvestConfigDTO> sysConfigList = autoBatchInvestService.querySysInvestConfigDTOList();
        response.setSysConfigList(sysConfigList);
        if(!StringUtils.isEmpty(userId)) {
        	 UserInvestConfig userCfg = autoBatchInvestService.queryUserInvestConfig(userId, InvestWayEnum.BATCH);
             UserInvestConfig userAutoCfg = autoBatchInvestService.queryUserInvestConfig(userId, InvestWayEnum.AUTO);
             response.setEnableAuto((byte)0);//默认不开启
     		if (Objects.nonNull(userAutoCfg) && Byte.valueOf("0").equals(userAutoCfg.getEnableSystem())) {
     			response.setEnableAuto(userAutoCfg.getEnableAuto());
     		}
             if (userCfg != null && Byte.valueOf("0").equals(userCfg.getEnableSystem())) { //系统未关闭
                 UserBatchInvestConfigDTO.UserConfig userConfig = new UserBatchInvestConfigDTO.UserConfig();
                 userConfig.setSingleMaxInvestAmount(userCfg.getSingleMaxInvestAmount());
                 userConfig.setMinKeepBalance(userCfg.getMinKeepBalance());
                 UserInvestPrizeConfig prizeCfg = autoBatchInvestService.queryUserInvestPrizeConfig(userId, InvestWayEnum.BATCH);
                 if (prizeCfg != null) {
                     userConfig.setAutoUsePrize(prizeCfg.getIsAutoUsePrize());
                     userConfig.setMustUsePrize(prizeCfg.getIsMustUsePrize());
                     userConfig.setUseCouponMinInvestAmount(prizeCfg.getUseCouponMinInvestAmount());
                 }
     			userConfig.setSyncToAutoInvest(userCfg.getSyncToAuto());
     			// 用户自动投资配置
     			// 选中的标的类型
     			List<Byte> productTypes = new ArrayList<>();
     			// 选中的期限
     			List<PeriodValue> periodTypes = new ArrayList<>();
     			// 选中的还款方式
     			List<Byte> repaymentTypes = new ArrayList<>();
     			// 选中的利率
     			List<BigDecimal> ratetTypes = new ArrayList<>();
     			userConfig.setProductTypes(productTypes);
     			userConfig.setPeriodTypes(periodTypes);
     			userConfig.setRepaymentTypes(repaymentTypes);
     			userConfig.setRatetTypes(ratetTypes);
     			autoBatchInvestService.generateUserConfigDetailForList(productTypes, periodTypes, repaymentTypes, ratetTypes, userCfg.getId());
     			response.setUserConfig(userConfig);
     		}
        }
       
		response.setDefaultConfig(queryInvestDefaultConfig());
		response.setMinInvestAmount(myLoanLocalService.getProjectStartInvestAndIncrement().getStartInvestMoney());
		return response;
    }

    @Override
    public void applyAutoInvestSetting(AutoInvestSettingRequestDTO dto) {
        String userId = dto.getUserId();
        ProjectStartInvestAndIncreResponseDTO startInvestAndIncre = myLoanLocalService.getProjectStartInvestAndIncrement();
        if(dto.getSingleMaxInvestAmount().compareTo(startInvestAndIncre.getStartInvestMoney())<0) {
        	throw new BizException("TRD10048");//单笔投资最大金额需要大于起投金额
        }
        AccountBaseResponse account = accountDubboService.loadNiiwooAccount(userId, UserRoleEnum.INVESTOR);
        accountDubboService.checkAccountAuth(account.getAccountId(), Sets.newHashSet(UserAuthorizationEnum.TENDER));
        investService.validateRiskEvaluating(userId);
        autoBatchInvestService.saveAutoInvestSetting(dto);
        try {
            //自动投资设置成功，发送MQ消息  -->用户是否新手任务
            NoviceTaskRecordRequestDTO sendDto = new NoviceTaskRecordRequestDTO();
            sendDto.setUserId(userId);
            sendDto.setTaskType(NoviceTaskPrizeEnum.OPEN_AUTO_INVEST.getCode());
            rabbitTemplate.convertAndSend(RabbitConstant.Exchange.NOVICE_TASK_RECORD, RabbitConstant.RoutingKey.NOVICE_TASK_RECORD, sendDto);
        } catch (Exception e) {
            logger.error("novice_task_activity-->设置自动投资mq发送失败 userId:{}", userId);
        }
        //自动投资委托授权合同入库
        ProjectContract contract = autoBatchInvestService.initCommissionAuthAgreement(userId, "NWZD", FadadaContractEnum.TradeTypeEnum.AUTO_INVEST);
        //发送MQ签署委托授权协议合同
        rabbitTemplate.convertAndSend(RabbitConstant.Exchange.SIGN_COMMISSION_AUTH_AGREEMENT, RabbitConstant.RoutingKey.SIGN_COMMISSION_AUTH_AGREEMENT, contract.getId());

	}

    @Override
    public void disableAutoInvest(String userId) {
        autoBatchInvestService.disableAutoInvest(userId);
    }

    @Override
    public void batchInvest(BatchInvestRequestDTO dto) {
        String userId = dto.getUserId();
        BigDecimal minInvestAmount = myLoanLocalService.getProjectStartInvestAndIncrement().getStartInvestMoney();
        //1、条件检查：投资余额 >= 100元，是否有正在处理中的批量投资
        AccountBaseResponse accountInfo = null;
        Byte userType = dto.getUserType();
        if(UserTypeEnum.PERSONAL.getValue().equals(userType)) {
        	accountInfo = accountDubboService.loadNiiwooAccount(userId, UserRoleEnum.INVESTOR);
        }else {
        	accountInfo = accountDubboService.getOrgAccount(userId, userId);
        }
        accountDubboService.checkAccountAuth(accountInfo.getAccountId(), Sets.newHashSet(UserAuthorizationEnum.TENDER));
        //查主库
        AccountBaseResponse accountBase =  accountDubboService.loadAccountByIdFromMaster(accountInfo.getAccountId());
        BigDecimal availableAmount = accountBase.getAvailableAmount();
        if (availableAmount.compareTo(minInvestAmount) < 0) {
            throw new BizException("TRD10003");
        }
        if (availableAmount.compareTo(dto.getMinKeepBalance().add(minInvestAmount)) < 0) {
            throw new BizException("TRD10015");
        }
        //有处理中的批量投资提示失败
        Boolean notExists = redisTemplate.opsForValue().setIfAbsent(TradeRedisKey.BATCH_INVESTING_LOCK + userId, System.currentTimeMillis() + "");
        if (notExists) {
            redisTemplate.expire(TradeRedisKey.BATCH_INVESTING_LOCK + userId, 15, TimeUnit.MINUTES);
        } else {
            throw new BizException("TRD10013");
        }
        investService.validateRiskEvaluating(userId);
        //2、保存批量投资设置
        autoBatchInvestService.saveBatchInvestSetting(dto);
        //3、同步批量投资设置到自动投资
        if (dto.getSyncToAutoInvest() == 1) {
            AutoInvestSettingRequestDTO autoDTO = new AutoInvestSettingRequestDTO();
            autoDTO.setUserId(userId);
            autoDTO.setPeriodTypes(dto.getPeriodTypes());
            autoDTO.setProductTypes(dto.getProductTypes());
            autoDTO.setRatetTypes(dto.getRatetTypes());
            autoDTO.setRepaymentTypes(dto.getRepaymentTypes());
            autoDTO.setSingleMaxInvestAmount(dto.getSingleMaxInvestAmount());
            autoDTO.setMinKeepBalance(dto.getMinKeepBalance());
            autoDTO.setIsAutoUsePrize(dto.getIsAutoUsePrize());
            autoDTO.setIsMustUsePrize(dto.getIsMustUsePrize());
            autoDTO.setUseCouponMinInvestAmount(dto.getUseCouponMinInvestAmount());
            autoBatchInvestService.saveAutoInvestSetting(autoDTO);
        }
        //4、投资人加入批量投资MQ队列
        //批量投资MQ处理
        rabbitTemplate.convertAndSend(RabbitConstant.Exchange.BATCH_INVESTOR, RabbitConstant.RoutingKey.BATCH_INVESTOR, dto);
        //批量投资委托授权合同入库
        ProjectContract contract = autoBatchInvestService.initCommissionAuthAgreement(userId, "NWPL", FadadaContractEnum.TradeTypeEnum.BATCH_INVEST);
        //发送MQ签署委托授权协议合同
        rabbitTemplate.convertAndSend(RabbitConstant.Exchange.SIGN_COMMISSION_AUTH_AGREEMENT, RabbitConstant.RoutingKey.SIGN_COMMISSION_AUTH_AGREEMENT, contract.getId());
    }

    @Override
    public boolean executeAutoInvest(Long projectId) {
    	long time = System.currentTimeMillis();
		ProjectInvesting project = investService.queryInvestingProjectInfo(projectId);
		if (project == null) {
			logger.info("自动投资===>标的条件不满足,可投资标的为空,或已满标,projectId={}", projectId);
			return true;
		}
		//非app渠道不进行自动投资
		if(!ChannelCodeEnum.APP.getChanelCode().equalsIgnoreCase(project.getChannelCode())) {
			logger.warn("自动投资，标的号{}，不属于app渠道，渠道为{}",projectId,project.getChannelCode());
			return true;
		}
		// 1：优先满足企业标，0：正常规则
		String priorityEnterprises = redisTemplate.opsForValue().get(TradeConfigEnum.PRIORITY_ENTERPRISES.getConfigKey());
		if (StringUtils.isBlank(priorityEnterprises)) {
			priorityEnterprises = tradeConfigLocalService.selectTradeConfigByEmun(TradeConfigEnum.PRIORITY_ENTERPRISES);
			
			if (StringUtils.isBlank(priorityEnterprises)) priorityEnterprises = "0";
			
			redisTemplate.opsForValue().set(TradeConfigEnum.PRIORITY_ENTERPRISES.getConfigKey(), priorityEnterprises);
		}
		// 不是企业标并且开启优先企业标，非企业标的全不投
		if ("1".equals(priorityEnterprises) && !Byte.valueOf("11").equals(project.getType())) {
			//原始逻辑进入暂存区，当前不进暂存区，已数据库数据为准
			logger.info("自动投资中断非企业标===>,priorityEnterprises:{},projectId={},type:{},", priorityEnterprises, projectId, project.getType());
			return true;
		}
		
		// 输出队列信息
//		investQueueService.logQueue();
		Byte projectStatus = project.getStatus();
		BigDecimal contractAmount = project.getContractAmount();
		BigDecimal investedAmount = project.getInvestedAmount();
		// 非可投资状态或标的剩余可投金额小于1元中断自动投资流程，ACK消息
		if (!projectStatus.equals(InvestableProjectStatusEnum.INVESTING.getValue()) || contractAmount.subtract(investedAmount).compareTo(BigDecimal.ONE) < 0) {
			logger.info("自动投资===>标的条件不满足,中断该标的自动投资,projectId={},status={},contractAmount={},investedAmount={}", projectId, projectStatus, contractAmount, investedAmount);
			return true;
		}
		
		//根据标的查询对应的产品类型&期限&还款方式&利率&加息后利率
		Set<String> matchedProduct = redisTemplate.boundZSetOps(TradeRedisKey.AUTO_INVESTORS_QUEUE_PREFIX+TradeRedisKey.AUTO_INVESTORS_QUEUE_PRODUCT+project.getLoanType().toString()).reverseRangeByScore(0, Integer.MAX_VALUE);
		Set<String> matchedRepayment = redisTemplate.boundZSetOps(TradeRedisKey.AUTO_INVESTORS_QUEUE_PREFIX+TradeRedisKey.AUTO_INVESTORS_QUEUE_REPAYMENT+project.getRepaymentType().toString()).reverseRangeByScore(0, Integer.MAX_VALUE); 
		Set<String> matchedPeriod = redisTemplate.boundZSetOps(TradeRedisKey.AUTO_INVESTORS_QUEUE_PREFIX+TradeRedisKey.AUTO_INVESTORS_QUEUE_DEANLINE+project.getDeadline().toString()+"~"+project.getDeadlineUnit().toString()).reverseRangeByScore(0, Integer.MAX_VALUE);
		Set<String> matchedRate = redisTemplate.boundZSetOps(TradeRedisKey.AUTO_INVESTORS_QUEUE_PREFIX+TradeRedisKey.AUTO_INVESTORS_QUEUE_RATE+project.getBorrowRate().stripTrailingZeros().toPlainString()).reverseRangeByScore(0, Integer.MAX_VALUE); 
		BigDecimal addRate = Objects.isNull(project.getActivityRaiseRate())?BigDecimal.ZERO:project.getActivityRaiseRate();
		Set<String> matchedAddRate = redisTemplate.boundZSetOps(TradeRedisKey.AUTO_INVESTORS_QUEUE_PREFIX+TradeRedisKey.AUTO_INVESTORS_QUEUE_RATE+project.getBorrowRate().add(addRate).stripTrailingZeros().toPlainString()).reverseRangeByScore(0, Integer.MAX_VALUE); 
		//产品&期限&还款方式 都不能为空，利率||加息后利率必须存在一个
		if(CollectionUtils.isEmpty(matchedProduct)
				||CollectionUtils.isEmpty(matchedRepayment)
				||CollectionUtils.isEmpty(matchedPeriod)
				||(CollectionUtils.isEmpty(matchedRate)&&CollectionUtils.isEmpty(matchedAddRate))) {
			logger.warn("自动投资===>没有匹配到对应的投资人,中断该标的自动投资,projectId={},deadline={},unit={},repayment={},rate={},addRate={}", projectId, project.getDeadline(), project.getDeadlineUnit(),project.getRepaymentType(),project.getBorrowRate(),addRate);
			return true;
		}
		//人员做交集，如果matchedRepayment不存在，matchedProduct将删除该userId
		matchedProduct.retainAll(matchedRepayment); 
		matchedProduct.retainAll(matchedPeriod);
		//为了便于后面做利率幅度，利率做最后匹配
		matchedRate.retainAll(matchedProduct);
		matchedAddRate.retainAll(matchedProduct);
		//合并所有结果，结果并入matchedRate
		matchedRate.addAll(matchedAddRate);
		
		//最后交集后，没有投资人
		if(CollectionUtils.isEmpty(matchedRate)) {
			logger.warn("自动投资===>最终匹配没有投资人,中断该标的自动投资,projectId={},deadline={},unit={},repayment={},rate={},addRate={}", projectId, project.getDeadline(), project.getDeadlineUnit(),project.getRepaymentType(),project.getBorrowRate(),addRate);
			return true;
		}
		//临时队列人员
		String tempProjectRedisKey = projectId+"-"+System.currentTimeMillis();
		try {
			//临时队列
			BoundListOperations<String, String> tempUserOps = redisTemplate.boundListOps(tempProjectRedisKey);
			InvestRoundStatus roundResult = null;
			logger.info("自动投资==>project{}，匹配人员数{}",projectId,matchedRate.size());
			for (String userId : matchedRate) {
				tempUserOps.rightPush(userId);
			} 
			do {
				//人员都不匹配或者标不能投资了，结束
				 roundResult = autoBatchInvestService.lookupAndInvest(projectId,tempUserOps);
			}while(tryNextInvestor(roundResult));
			
		} catch (Exception e) {
			logger.error("自动投资异常,project{}",projectId, e);
		}finally {
			redisTemplate.delete(tempProjectRedisKey);
		}
		logger.info("自动投资标的{},总耗时{}",projectId,System.currentTimeMillis()-time);
        return true;
    }
    /**
     * 是否可以继续投资
     * @param status
     * @return
     */
    private boolean tryNextInvestor(InvestRoundStatus status) {
        return InvestRoundStatus.INVESTOR_CAUSE_UNMATCH.equals(status)
                || InvestRoundStatus.SETTINGS_CAUSE_UNMATCH.equals(status)
                || InvestRoundStatus.INVEST_SUCCESS_NOT_FULL.equals(status)
//                || RoundStatus.LOCK_INVESTTING.equals(status)
                || InvestRoundStatus.SELF_BORROW_PROJECT.equals(status)
                || InvestRoundStatus.INVEST_STOP_BY_EVALUATION_DEAL.equals(status);
    }
   

    /**
     * 根据用户的批量投资设置（标的类型、期限、利率等）筛选出可投资标的按发布时间升序取x条标的执行投资
     *
     * @param mqMsg mq消息
     * @return
     */
    @Override
    public boolean executeBatchInvest(BatchInvestRequestDTO mqMsg) {
        String userId = mqMsg.getUserId();
        try {
            doBatchInvest(mqMsg, 30);
        } catch (Exception e) {
            logger.error("批量投资===>执行批量投资失败,userId={}", userId, e);
        } finally {
            //执行完该次批量投资删除批量投资锁
            redisTemplate.delete(TradeRedisKey.BATCH_INVESTING_LOCK + userId);
        }
        return true;
    }

    @Override
    public void changeAutoInvestPriority(List<InvestPriorityChangeRequest> changeList) {
        Assert.notEmpty(changeList, "修改用户投资级别数据不能为空");
        autoBatchInvestService.changeAutoInvestPriority(changeList);
    }
    /**
     * 系统关闭
     */
    @Override
    public void disableAllAutoInvest() {
    	investQueueService.cleanAll();
        autoBatchInvestService.disableAllAutoInvest();
        logger.info("关闭所有自动投资完成");
    }

    @Override
    public void cleanAllBatchInvestSetting() {
        investService.cleanAllBatchInvestSetting();
        logger.info("清除所有批量投资设置完成");
    }

    @Override
    public PageResponseDTO<CommissionAuthAgreementResponseDTO> queryCommissionAuthHistory(CommissionAuthHistoryRequestDTO request) {
        String userId = request.getUserId();
        int offset = request.getOffset();
        int pageSize = request.getPageSize();
        TradeTypeEnum tradeTypeEnum = request.getTradeType();
        return investService.queryCommissionAuthHistory(userId, tradeTypeEnum.getType(), offset, pageSize);
    }

    @Override
    public void reloadAutoInvestorQueue() {
        investQueueService.logQueue();
        autoBatchInvestService.revertAutoInvest(System.currentTimeMillis());
        autoBatchInvestService.revertProject(System.currentTimeMillis());
        investQueueService.logQueue();
    }
    
    @Override
    public void reloadAllAutoInvestorQueue() {
        investQueueService.logQueue();
        //清空所有队列
        Set<String> keys = redisTemplate.keys(TradeRedisKey.AUTO_INVESTORS_QUEUE_PREFIX + "*");
        if(CollectionUtils.isNotEmpty(keys)) {
        	redisTemplate.delete(keys);
        }
        //清空休息区
        redisTemplate.delete(TradeRedisKey.REST_AREA_NO_RED_ENVELOPES);
        redisTemplate.delete(TradeRedisKey.REST_AREA_NOT_SUFFICIENT_FUNDS);
        redisTemplate.delete(TradeRedisKey.REST_AREA_RISK_FUND);
        redisTemplate.delete(TradeRedisKey.REST_AREA_UNKNOW);
        autoBatchInvestService.reloadAllAutoInvestorQueue();
        investQueueService.logQueue();
    }
    @Override
    public void onSysInvestSettingAdded(SysInvestConfigDTO sysInvestConfigDTO) {
    	// 5.4.0 需求是 用户保留历史记录，因此新记录与变更记录不需要重置队列
    	//用户新配置或者修改配置才需要修改队列
    	return;
    }

    private void doBatchInvest(BatchInvestRequestDTO mqMsg, int maxInvestCount) {
        String userId = mqMsg.getUserId();
        Byte userType = mqMsg.getUserType();
        List<BatchInvestFinishedMsg.Entry> succeedEntryList = new ArrayList<>();
        BigDecimal minInvestAmount = BigDecimal.valueOf(100);
        logger.info("批量投资==》用户{},设置的筛选条件产品类型{},利率{},还款方式{},期限{}",userId,mqMsg.getProductTypes(),mqMsg.getRatetTypes(),mqMsg.getRepaymentTypes(),mqMsg.getPeriodTypes());
        //筛选标的
        List<ProjectInvesting> projects = findCandidateInvestProjects(userId,mqMsg.getProductTypes(),mqMsg.getRatetTypes(),mqMsg.getRepaymentTypes(),mqMsg.getPeriodTypes());
        //没有找到标的，终止
        if (projects.isEmpty()) {
            logger.info("批量投资===>没有找到可投资标的,结束本次批量投资,userId={}", userId);
            sendBatchInvestResultMsg(userId, succeedEntryList);
            return;
        }
        logger.info("批量投资===>匹配该用户投资设置的标的共{}个,userId={},projects={}", projects.size(), userId, projects.stream().map(ProjectInvesting::getProjectId).collect(Collectors.toList()));
        
        boolean autoUsePrize = mqMsg.getIsAutoUsePrize().intValue() == 1;
        boolean mustUsePrize = mqMsg.getIsMustUsePrize().intValue() == 1;
        long batchNo = snowflakeIdWorker.nextId();
        AccountBaseResponse account = null;
        if(UserTypeEnum.ORGANIZATION.getValue().equals(userType)) {
        	account = accountDubboService.getOrgAccount(userId, userId);
        }else {
        	account = accountDubboService.loadNiiwooAccount(userId, UserRoleEnum.INVESTOR);
        }
        for (ProjectInvesting project : projects) {
        	//查询账户信息，检查余额、免密投资状态
            AccountBaseResponse accountInfo = accountDubboService.loadAccountByIdFromMaster(account.getAccountId());
            if (accountInfo.getTender() != 1) {
                logger.info("批量投资===>未开启免密投资,中断本次批量投资,userId={}", userId);
                sendBatchInvestResultMsg(userId, succeedEntryList);
                return;
            }
            BigDecimal availableAmount = accountInfo.getAvailableAmount();
            if (availableAmount.compareTo(minInvestAmount) < 0) {
                logger.info("批量投资===>投资账户余额不足,中断本次批量投资,userId={},availableAmount={}", userId, availableAmount);
                sendBatchInvestResultMsg(userId, succeedEntryList);
                return;
            }
            BigDecimal minKeepBalance = mqMsg.getMinKeepBalance();
            if (availableAmount.compareTo(minKeepBalance.add(minInvestAmount)) < 0) {
                logger.info("批量投资===>投资账户余额不满足最低保留余额,中断本次批量投资,userId={},availableAmount={},minKeepBalance={}", userId, availableAmount, minKeepBalance);
                sendBatchInvestResultMsg(userId, succeedEntryList);
                return;
            }
            Long projectId = project.getProjectId();
            //获取当前风险等级对应的剩余可投资金额.
            BigDecimal surplusCanInvestAmount = investCommonServer.getSurplusCanInvestAmount(userId);
            if (surplusCanInvestAmount.compareTo(BigDecimal.ZERO) == 0) {
                logger.info("批量投资===>当前投资金额已达该风险类型投资上限,中断批量投资,projectId={},userId={}", projectId, userId);
                //发送 mq 当前投资金额已达该风险类型投资上限处理
                investService.notifyInvestStopForEvaluation(userId, InvestFromTypeEnum.AUTOMATIC_INVEST);
                break;
            }
            UserInvestConfig userCfg = new UserInvestConfig();
            userCfg.setUserId(userId);
            userCfg.setMinKeepBalance(minKeepBalance);
            userCfg.setSingleMaxInvestAmount(mqMsg.getSingleMaxInvestAmount());
            BigDecimal investAmount = autoBatchInvestService.calcInvestAmount(project, availableAmount, userCfg, surplusCanInvestAmount);
            if(BigDecimal.ZERO.compareTo(investAmount) == 0) {
            	logger.info("批量投资===>投资账户余额不足百元,结束本次批量投资,userId={},availableAmount={}", userId, availableAmount);
                break;
            }
            Long prizeId = null;
            if (autoUsePrize) {
                prizeId = investPrizeService.querySuitablePrize(userId, project, investAmount, mqMsg.getUseCouponMinInvestAmount());
                logger.info("批量投资===>可用红包,prizeId={},projectId={},userId={},investAmount={}", prizeId, projectId, userId, investAmount);
            }
            //必须使用红包而又没有可用红包
            if (autoUsePrize && mustUsePrize && prizeId == null) {
            	boolean iaMatch = investPrizeService.valdatePrize(userId,mqMsg.getUseCouponMinInvestAmount(),availableAmount);
                //余额较少，中断
                if (!iaMatch) {
                    logger.info("批量投资===>必须使用红包而又没有合适红包,中断批量投资,userId={},projectId={},availableAmount={},investAmount={}", userId, projectId, availableAmount, investAmount);
                    break;
                } else {
                    //余额较多，跳过当前标的，继续尝试下一个标的
                    logger.info("批量投资===>必须使用红包有红包但是不匹配当前标,跳过该标的,userId={},projectId={},availableAmount={},investAmount={}", userId, projectId, availableAmount, investAmount);
                    continue;
                }
            }
            //调用投资
            InvestDubboRequest investReq = new InvestDubboRequest();
            investReq.setUserId(userId);
            investReq.setInvestAmount(investAmount);
            investReq.setProjectId(projectId);
            investReq.setPrizeId(prizeId);
            investReq.setInvestUserType(userType);
            try {
                Long investOrderId = autoBatchInvestService.invest(userId, projectId, InvestWayEnum.BATCH, batchNo, accountInfo, availableAmount, investAmount, investReq);
                availableAmount = availableAmount.subtract(investAmount);
                BatchInvestFinishedMsg.Entry entry = new BatchInvestFinishedMsg.Entry();
                entry.setInvestOrderId(investOrderId);
                entry.setProjectId(projectId);
                entry.setProjectType(project.getType());
                entry.setInvestAmount(investAmount);
                entry.setRepaymentType(project.getRepaymentType());
                entry.setYearRate(project.getBorrowRate());
                entry.setDeadline(project.getDeadline());
                entry.setDeadlineUnit(project.getDeadlineUnit());
                succeedEntryList.add(entry);
            } catch (BizException e) {
                String errorCode = e.getErrorCode();
                //已满标
                if ("TRD10007".equals(errorCode)) {
                    logger.error("批量投资===>批量投资执行投资出错,跳过该标的,projectId={},userId={}", projectId, userId, e);
                    continue;
                } else {
                    //用户原因
                    logger.error("批量投资===>批量投资执行投资出错,结束本次批量投资,projectId={},userId={}", projectId, userId, e);
                    break;
                }
            } catch (Exception e) {
                logger.error("批量投资===>批量投资执行投资出错,结束本次批量投资,projectId={},userId={}", projectId, userId, e);
                break;
            }
        }
        sendBatchInvestResultMsg(userId, succeedEntryList);
    }

    private void sendBatchInvestResultMsg(String userId, List<BatchInvestFinishedMsg.Entry> succeedEntryList) {
        //发送MQ消息，异步统计投资预期收益等信息后发送通知给投资人
        BatchInvestFinishedMsg msg = new BatchInvestFinishedMsg(userId, succeedEntryList, new Date());
        rabbitTemplate.convertAndSend(RabbitConstant.Exchange.BATCH_INVEST_FINISHED, RabbitConstant.RoutingKey.BATCH_INVEST_FINISHED, msg);
        logger.info("批量投资===>批量投资执行完毕,发送MQ消息,userId={},msg={}", userId, JSON.toJSONString(msg));
    }

    
    /**
     * 查询符合条件的批量投资的标
     * @param userId
     * @param productTypes
     * @param ratetTypes
     * @param repaymentTypes
     * @param periodTypes
     * @return
     */
    private List<ProjectInvesting> findCandidateInvestProjects(String userId, List<Integer> productTypes, List<BigDecimal> ratetTypes, List<Integer> repaymentTypes, List<PeriodValue> periodTypes) {
    	List<ProjectInvesting> result = new  ArrayList<>();
    	List<ProjectInvesting> queryResult = autoBatchInvestService.queryAllInvestingProjectWithCondition(userId,productTypes,repaymentTypes);
    	if(CollectionUtils.isEmpty(queryResult)) {
    		return result;
    	}
    	//利率  期限 后续可能做浮动，单独出来做匹配
    	List<String> rateStrings = new ArrayList<>();
    	List<String> periodTypesString = new ArrayList<>();
    	for (BigDecimal rate : ratetTypes) {
    		rateStrings.add(rate.stripTrailingZeros().toPlainString());
		}
    	for (PeriodValue period : periodTypes) {
    		periodTypesString.add(period.getDeadline()+"~"+period.getDeadlineUnit());
		}
    	
    	return queryResult.stream().filter(project->canInvestProject(project,rateStrings,periodTypesString) ).collect(Collectors.toList());
    	 
    }
    /**
     * 验证是否可投资
     * @param project
     * @param periodTypesString 
     * @param rateStrings 
     * @return
     */
	private boolean canInvestProject(ProjectInvesting project, List<String> rateStrings, List<String> periodTypesString) {
		BigDecimal addRate = Objects.nonNull(project.getActivityRaiseRate()) ? project.getActivityRaiseRate() : BigDecimal.ZERO;
		String period = project.getDeadline() + "~" + project.getDeadlineUnit();
		String rateString = project.getBorrowRate().stripTrailingZeros().toPlainString();
		String addRateString = project.getBorrowRate().add(addRate).stripTrailingZeros().toPlainString();
		// 利率匹配借款利率或者匹配借款利率+加息利率并且匹配期限
		return (rateStrings.contains(rateString) || rateStrings.contains(addRateString)) && periodTypesString.contains(period);
	}
	
	@Override
	public void updateReadFalg(String userId) {
		 autoBatchInvestService.updateReadFalg(userId);
	}
	
	
	@Override
	public void dataTransfer() {
		
		autoBatchInvestService.dataTransfer();
	}
	
}
